<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>JsPlumb Draw</title>
    <!-- 引入layui的主要样式表 -->
    <link rel="stylesheet" href="../layui/css/layui.css">
    <!-- 引入jQueryUI的样式表 -->
    <link rel="stylesheet" href="../jsplumb/jquery-ui.min.css">
    <!-- 引入流程设计的样式 -->
    <link rel="stylesheet" href="../jsplumb/flow_design.css">
    <!-- 引入shape的样式 -->
    <link rel="stylesheet" href="../jsPlumbDraw/shape.css">
    <!-- 引入formSelects多选框样式 -->
    <link rel="stylesheet" href="../jsPlumbDraw/formSelects-v4.css">
    <style>
    * {
        margin: 0;
        padding: 0;
    }
    body .layer-ext-myskin .layui-layer-content {
        overflow: visible;
    }
    .layui-fluid {
        padding: 5px;
    }
    .layui-card {
        border: 1px solid #eeeeee;
    }
    .layui-card-header {
        border-bottom: 1px solid #5FB878;
    }
    .btn-container {
        max-height: 800px;
	    overflow-y: auto;
    }
    #flow-container {
        min-height: 600px;
		background-image: linear-gradient(90deg, rgba(156, 144, 144, 0.15) 10%, rgba(0, 0, 0, 0) 10%), linear-gradient(rgba(105, 99, 99, 0.15) 10%, rgba(0, 0, 0, 0) 10%);
		background-size: 10px 10px;
		position: relative;
		overflow: auto;
    }
    .helpText {
        font-size: 14px;
    }
    .lineType a {
        display: block;
        color: #ffffff;
    }
    .lineType a:hover {
        color: black;
        border-bottom: 1px solid #fff;
    }
    </style>
</head>
<body>
<div class="layui-fluid">
    <div class="layui-card">
        <div class="layui-card-body">
            <div class="layui-btn-group">
                <button type="button" class="layui-btn layui-btn-sm" title="保存" data-type="save">
                    <i class="layui-icon">&#xe655;</i>
                </button>
                <!-- <button type="button" class="layui-btn layui-btn-sm" title="添加节点">
                    <i class="layui-icon">&#xe61f;</i>
                </button>
                <button type="button" class="layui-btn layui-btn-sm" title="编辑点击元素">
                    <i class="layui-icon">&#xe642;</i>
                </button>
                <button type="button" class="layui-btn layui-btn-sm" title="删除点击元素">
                    <i class="layui-icon">&#xe640;</i>
                </button> -->
                <button type="button" class="layui-btn layui-btn-sm" title="连线" data-type="drawLineMenu" id="drawLineMenu">
                    <i class="layui-icon">&#xe674;</i>
                </button>
                <button type="button" class="layui-btn layui-btn-sm" title="剪贴板" data-type="clipboard">
                    <i class="layui-icon">&#xe630;</i>
                </button>
                <button type="button" class="layui-btn layui-btn-sm" title="提示及帮助" data-type="help">
                    <i class="layui-icon">&#xe60b;</i>
                </button>
            </div>
        </div>
    </div>
    <div class="layui-row layui-col-space10">
        <div class="layui-col-md2">
            <div class="layui-card">
				<div class="layui-card-header">步骤块区</div>
				<div class="layui-card-body">
					<div class="btn-container">
                        <!-- 放置步骤区块 -->
                        <div class="shape diamond_outer" data-shape="diamond" title="菱形(决策)">
                            <div class="diamond_text">菱形(决策)</div>
                            <div class="diamond_top"></div>
                            <div class="diamond_bottom"></div>
                        </div>
                        <div class="shape rectangle_outer" data-shape="rectangle" title="矩形(过程)">
                            <div class="rectangle_text">矩形(过程)</div>
                            <div class="rectangle_top"></div>
                            <div class="rectangle_bottom"></div>
                        </div>
                        <div class="shape roundedRect_outer" data-shape="roundedRect" title="圆角矩形(可选过程)">
                            <div class="roundedRect_text">圆角矩形(可选过程)</div>
                            <div class="roundedRect_top"></div>
                            <div class="roundedRect_bottom"></div>
                        </div>
                        <div class="shape arcRect_outer" data-shape="arcRect" title="圆弧矩形(开始/结束)">
                            <div class="arcRect_text">圆弧矩形(开始/结束)</div>
                            <div class="arcRect_top"></div>
                            <div class="arcRect_bottom"></div>
                        </div>
                        <div class="shape hexagon_outer" data-shape="hexagon" title="六边形(准备)">
                            <div class="hexagon_text">六边形(准备)</div>
                            <div class="hexagon_top"></div>
                            <div class="hexagon_bottom"></div>
                        </div>
					</div>
				</div>
			</div>
        </div>
        <div class="layui-col-md10">
            <div class="layui-card">
				<div class="layui-card-header">流程图示区</div>
				<div class="layui-card-body">
					<div class="flow-container" id="flow-container">
						<!-- 放置流程设计图的容器 -->
					</div>
				</div>
			</div>
        </div>
    </div>
</div>

<!-- 克隆到流程容器的模板节点 -->
<script type="text/html" id="shapeNode">
    <div id="{{ d.id }}" data-shape="{{ d.shape }}" data-json="{{= JSON.stringify(d) }}" style="top: {{ d.top }}px; left: {{ d.left }}px; cursor: Move;" class="item-shape-node {{ d.shape }}_outer">
        <i class="layui-icon layui-icon-close-fill"></i>
        <div class="{{ d.shape }}_text">
            <span class="nodeName" title="{{ d.name }}">{{ d.name }}</span>
        </div>
        <div class="{{ d.shape }}_top"></div>
        <div class="{{ d.shape }}_bottom"></div>
    </div >
</script>

<script type="text/html" id="bar">
    <a class="layui-btn layui-btn-xs" lay-event="pasteTpl">粘贴模板</a>
    <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>

<script type="text/html" id="helpText">
    <div class="helpText">
        <p>1. 顶栏菜单按钮，分别是：保存、连线、剪贴板</p>
        <p>--1.2. <i style="color: red;">保存</i> 按钮的功能是：当编辑好流程图后，点击保存，程序会保存流程图及关系</p>
        <p>--1.3. <i style="color: red;">连线</i> 按钮的功能是：用户可以选择几种连线的样式，主要是带标签的，如：<span style="color: green; font-weight: bold;">YES</span>、<span style="color: red; font-weight: bold;">NO</span>、<span style="color: grey; font-weight: bold;">回退/驳回</span>...</p>
        <p>--1.4. <i style="color: red;">剪贴板</i> 按钮的功能是：将复制的节点放置在这个面板中，用户可以点击想要复制的模板到容器中</p>
        <p>2. 拖拽到流程图示区节点与流程图示区都有右键菜单功能</p>
        <p>--2.1. 流程图示区节点右键菜单,分别有：复制节点、删除节点、编辑表单、编辑节点名称</p>
        <p>----2.1.1. <i style="color: red;">复制节点</i> 的功能是：点击后，程序会将此节点当成模板保存在剪贴板中，等待下一步操作<i style="color: red;">粘贴节点</i></p>
        <p>----2.1.2. <i style="color: red;">删除节点</i> 的功能是：点击后，程序会将此节点删除</p>
        <p>----2.1.3. <i style="color: red;">编辑表单</i> 的功能是：未完成...待开发</p>
        <p>----2.1.4. <i style="color: red;">编辑节点名称</i> 的功能是：可以编辑当前所点击的节点名称，双击节点文本值也可打开编辑</p>
        <p>--2.2. 流程图示区右键菜单，分别有：粘贴节点、打开剪贴板</p>
        <p>----2.2.1. <i style="color: red;">粘贴节点</i> 的功能是：右键点击后，程序会在右键点击的地方粘贴一个近期复制过的一个节点</p>
        <p>----2.2.1. <i style="color: red;">打开剪贴板</i> 的功能是：将复制的节点放置在这个面板中，用户可以点击想要复制的模板到容器中</p>
    </div>
</script>

<script type="text/html" id="lineType">
    <div class="lineType">
        <a href="javascript:;" data-line="1">正常流程逻辑线（与签）</a>
        <a href="javascript:;" data-line="5">或签</a>
        <a href="javascript:;" data-line="2">判断流程逻辑线（YES）</a>
        <a href="javascript:;" data-line="3">判断流程逻辑线（NO）</a>
        <a href="javascript:;" data-line="4">回退/驳回线</a>
    </div>
</script>

<script type="text/html" id="nodeInfo">
    <form action="" onsubmit="return false;" class="layui-form layui-form-pane" lay-filter="logic-form" style="padding: 30px 30px 0;">
        <input type="hidden" name="id" value="">
        <input type="hidden" name="flowId" value="">
        <fieldset class="layui-elem-field layui-field-title">
            <legend style="font-size: 14px;font-weight: bold">步骤信息</legend>
        </fieldset>
        <div class="layui-form-item">
            <label class="layui-form-label">名称</label>
            <div class="layui-input-block">
                <input name="name" placeholder="请输入名称" lay-verify="required" class="layui-input" value="" autocomplete="off" />
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">操作所属</label>
            <div class="layui-input-block">
                <select name="operatorType" id="operatorType" lay-verify="required" lay-filter="operatorType">
                    <option value="">请选择</option>
                    <option value="1">部门</option>
                    <option value="2">人员</option>
                </select>
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">操作人/部门</label>
            <div class="layui-input-block">
                <select name="operator" id="operator" lay-verify="required" xm-select="operator" xm-select-search=""
                    lay-filter="operator" xm-select-height="36px">
                    <option value="2">人员</option>
                </select>
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">超时时间</label>
            <div class="layui-input-inline">
                <input name="timeout" placeholder="请输入超时时间" lay-verify="required" class="layui-input" value="0"
                    autocomplete="off" />
            </div>
            <div class="layui-form-mid layui-word-aux"> 0 为不超时，单位为 h （时）</div>
        </div>
        <div class="layui-form-item" pane>
            <label class="layui-form-label">状态</label>
            <div class="layui-input-block">
                <input type="checkbox" name="status" lay-skin="switch" lay-text="启用|禁用" checked>
            </div>
        </div>
        <div class="layui-form-item layui-form-text">
            <label class="layui-form-label">备注：</label>
            <div class="layui-input-block">
                <textarea placeholder="请输入备注" class="layui-textarea" name="mark"></textarea>
            </div>
        </div>
        <div class="layui-form-item" align="center">
            <button class="layui-btn layui-btn-sm" lay-filter="saveConfig" lay-submit>保存此步骤配置</button>
            <button type="reset" class="layui-btn layui-btn-sm layui-btn-primary">重置</button>
        </div>
    </form>
</script>

<!-- 引入layui的js文件 -->
<script src="../layui/layui.js"></script>
<script>
layui.config({
    base: './' // 配置layui第三方插件存放的基础路径，自由更改
}).extend({
    jqui: '../jsplumb/jQueryUI.min',
    jsPlumb: '../jsplumb/jquery_jsplumb_forlayui.min',
    draw: 'config',
    formSelects: 'formSelects-v4.min'
}).use(['laytpl', 'layer', 'jsPlumb', 'jqui', 'draw', 'table', 'formSelects', 'element', 'form'], function() {
    var $ = layui.jquery, laytpl = layui.laytpl, jsPlumb = layui.jsPlumb, draw = layui.draw, form = layui.form,
        layer = layui.layer, table = layui.table, formSelects = layui.formSelects, element = layui.element;

    // 用于存储或获取本地数据的变量，字符串的数组'[edges, nodes]';
    var storage_items = localStorage.getItem('storage_items') ? JSON.parse(localStorage.getItem('storage_items')) : '';
    // 记录删除节点的信息id，字符串数组'[edgeIds, nodeIds]'
    var storage_delIds = localStorage.getItem('storage_delIds') ? JSON.parse(localStorage.getItem('storage_delIds')) : '',
        nodeIds = [], edgeIds = [];

    if (typeof (Storage) === "undefined") {
        var msg = '对不起，您的浏览器不支持本地存储数据，请使用 Internet Explorer 8以上 的主流浏览器访问，如：Firefox, Opera, Chrome...';
        layer.msg(msg, function () {
            // history.back(-1);
            return false;
        });
    }
    console.log(storage_items, storage_delIds);
    
    // 存储复制操作时的临时存储模板变量，tplArr -> tplJson ，pasteTpl 是粘贴时的对象有且只有一个
    var tplArr = [], tplJson = [], pasteTplId = -1;

    var fun = {
        mockMakeUUID: function() { // 模拟生成uuid
            return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
        },
        guid: function() { // 生成uuid的长串
            return (fun.mockMakeUUID() + fun.mockMakeUUID() + "-" + fun.mockMakeUUID() + "-" + fun.mockMakeUUID() + "-" 
                    + fun.mockMakeUUID() + "-" + fun.mockMakeUUID() + fun.mockMakeUUID() + fun.mockMakeUUID());
        },
        renderShapeNode: (_data) => {
            var uuid, data;
            // 判断传过来的数据_data数据是否存在id这一属性
            if (_data.id === undefined) {
                uuid = fun.guid();
                data = $.extend(true, {
                    id: uuid
                }, _data);
            }else{ // 如果存在，那说明是重绘图，则不需要重新定义uuid
                data = _data;
                uuid = _data.id;
            }
            laytpl($('#shapeNode').html()).render(data, function(_html) {
                $('#flow-container').append(_html);
            });
            fun.createPoint2Drop(uuid);
        },
        createPoint2Drop: (_uuid) => {
            jsPlumb.makeSource(_uuid, {
                anchor: [[0.2, 0, 0, 0], "Top", "Right", "Bottom", "Left", "BottomLeft", /*"TopRight",*/ "TopLeft", "BottomRight"], // 可以使用 Continuous 属性，产生锚点位置
                // allowLoopback: false, //禁止回环
                filter: function (_ev, _ele) {
                    var tar = _ev.target.tagName;
                    if (tar === 'SPAN' || tar === 'I') {
                        // 过滤这两个标签不创造端点
                        return false;
                    }
                },
            }, draw.config);
            jsPlumb.draggable(_uuid, {
                containment: "parent",
                // grid: [5, 5]
            });
        },
        cloneShapeNode: () => {
            $('.shape').draggable({
                helper: 'clone',
                scope: "shapeNode"
            });
            $('#flow-container').droppable({
                scope: "shapeNode",
                drop: function (_event, _ui) {
                    var data = {
                        shape: _ui.helper.data('shape'),
                        name: _ui.helper.text().replace(/(^\s*)|(\s*$)/g, ""),
                        top: _ui.position.top,
                        left: _ui.position.left - $('.layui-col-md2').outerWidth(),
                        itemJson: {}
                    };
                    fun.renderShapeNode(data);
                }
            });
        },
        drawLine: (_edge) => {
            // 这个是重绘图的画线，系统绘线
            if (!_edge || !$.isPlainObject(_edge)) {
                layer.msg('线的数据源不存在');
                return false;
            }
            jsPlumb.makeTarget(_edge.childStep, {
                anchor: [[0.2, 0, 0, 0], "Top", "Right", "Bottom", "Left", "BottomLeft", /*"TopRight",*/ "TopLeft", "BottomRight"],
                allowLoopback: false,
            }, draw.config);
            var comm = {
                overlays: [
                    ["Arrow", { location: 1, width: 10, length: 10 }],
                    ['Label', {}]
                ]
            };
            if (_edge.relationType == '1') {
                if (_edge.relationAndOr == '1') {
                    comm.overlays[1] = ['Label', {
                        label: '或签',
                        cssClass: 'jsplumb-label',
                    }];
                }else if(_edge.relationAndOr == '2') {
                    comm.overlays[1] = ['Label', {}];
                }
            }else if (_edge.relationType == '2') {
                if (_edge.relationFlag == '1') {
                    comm.overlays[1] = ['Label', {
                        label: 'YES',
                        cssClass: 'jsplumb-label',
                    }];
                } else if (_edge.relationFlag == '-1') {
                    comm.overlays[1] = ['Label', {
                        label: 'NO',
                        cssClass: 'jsplumb-label',
                    }];
                }
            } else if (_edge.relationType == '3') {
                comm.overlays[1] = ['Label', {
                    label: '回退/驳回',
                    cssClass: 'jsplumb-label',
                }];
            }
            jsPlumb.connect({
                source: _edge.parentStep,
                target: _edge.childStep,
                parameters: _edge, // 将线的数据都存储在这个属性中
            }, comm);
        }
    };

    var active = {
        delNode: function(_nodeId) {
            var r = /([a-zA-Z]+[0-9]+|[0-9]+[a-zA-Z])/;
            if (!r.exec(_nodeId)) { // 检测id如果不是数字字母混合而成的，那就是数据库的主键id了
                nodeIds.push(_nodeId); // 记录在库，提交到后台时，方便删除无用节点
            }
            jsPlumb.remove(_nodeId);
        },
        save: function() {
            var b = 1;
            var edges = jsPlumb.getAllConnections(), edgesArr = []; // 可以获取所有的连线集
            var nodes = $('#flow-container').children('.item-shape-node'), nodesArr = []; // 获取所有的节点
            // 处理线的逻辑
            $.each(edges, (i, v) => {
                var /*edgesId = v.id, */label = v.canvas.nextSibling.innerText;
                var relationType = 0, relationFlag = 0, relationAndOr = 0;
                switch (label) {
                    case '或签':
                        relationType = 1;
                        relationAndOr = 1;
                        break;
                    case '':
                        relationType = 1;
                        relationAndOr = 2;
                        break;
                    case 'YES':
                        relationType = 2;
                        relationFlag = 1;
                        break;
                    case 'NO':
                        relationType = 2;
                        relationFlag = -1;
                        break;
                    case '回退/驳回':
                        relationType = 3;
                        break;
                    default:
                        break;
                }
                var edge = {
                    // id: edgesId,
                    parentStep: v.sourceId,
                    childStep: v.targetId,
                    relationType,
                    relationFlag,
                    relationAndOr,
                };
                edgesArr.push(edge);
            });
            // 处理节点数据
            $.each(nodes, (i, v) => {
                var node = $(v).data('json');
                node = $.extend(true, node, {
                    left: parseInt($(v).css('left')),
                    top: parseInt($(v).css('top')),
                });
                if (node.operatorType === undefined || node.operator === undefined || node.timeout === undefined) {
                    b = 0;
                    layer.msg('节点为：<span style="color: red;">' + node.name + '</span> 步骤信息没有填写完整，请右键<span style="color: red;">`编辑节点信息`</span>，再保存流程');    
                    return false;
                }
                nodesArr.push(node);
            });
            if (!b) {
                return false;
            }
            if (!active.isReasonable(edgesArr, nodesArr)) {
                return false;
            }
            if (!active.findFirstStep(edgesArr, nodesArr)) {
                return false;
            }
            // console.log(edgesArr, nodesArr);
            localStorage.setItem('storage_items', JSON.stringify([edgesArr, nodesArr]));
            localStorage.setItem('storage_delIds', JSON.stringify([edgeIds, nodeIds]));
        },
        editForm: function() {
            layer.closeAll();
            layer.open({
                title: false,
                content: '<div>11</div>',
                area: ['80%'],
                btn: false,
                shadeClose: true,
                cancel: function() {
                    // 点击右上角的关闭按钮，触发回调
                }
            });
        },
        del: function(_id) {
            layer.confirm('是否要删除此节点', { icon: 3, title: '提示' }, function () {
                active.delNode(_id);
                layer.closeAll();
            }, function (_index) {
                layer.msg('你已取消删除此节点操作');
                layer.close(_index);
            });
        },
        editNodeName: function(_id) {
            layer.closeAll();
            // 查找到相关节点的nodeName的文本，并将前后的空格去掉
            var t = $('#' + _id).find('.nodeName').text().replace(/(^\s*)|(\s*$)/g, "");
            layer.prompt({ title: '重新命名文本框', formType: 0, value: t }, function(_text, _index) {
                layer.close(_index);
                $('#' + _id + ' .nodeName').text(_text); // 将新值赋值到所点击的元素上
                $('#' + _id + ' .nodeName').attr('title', _text); // 将新值赋值到所点击的元素上
                $('#' + _id).data('json', $.extend($('#' + _id).data('json'), { name: _text }));
            });
        },
        copyNode: function(_id) {
            layer.closeAll();
            // 生成uuid，担有个缺点，多次引用会产生多个不同的uuid，去除clone时产生的多余类名（必须）
            var h = $('#' + _id).clone().removeClass('ui-droppable ui-draggable ui-draggable-handle item-shape-node').removeAttr('id').removeAttr('style');
            // 放进预先定义好的模板数组里
            tplArr.push(h);
            layer.msg('节点已复制');
            return false;
        },
        pasteNode: (_id, _xy) => {
            // 粘贴节点
            layer.closeAll();
            if (!_xy) {
                _xy = [20, 20];
            }
            if ($.isArray(tplArr) && tplArr.length > 0) {
                var tplId = -1;
                if (pasteTplId > -1) {
                    tplId = pasteTplId;
                }else{
                    tplId = tplArr.length - 1;
                }
                // 为即将复制的模板节点做一些改变，改变唯一id，和改变定位样式，总是调用数组中最后一个为粘贴的对象
                var tpl = tplArr[tplId].attr('id', fun.guid()).css({'left': _xy[0], 'top': _xy[1]}).addClass('item-shape-node');
                $('#flow-container').append(tpl);
                // 使新clone的节点能移动和连线，必须从clone的节点获取到id在渲染
                fun.createPoint2Drop(tpl.attr('id'));
                // 为避免将模板节点粘贴完以后会出现第二次粘贴时，覆盖第一次的粘贴的节点，导致多次粘贴以后还是只有一个粘贴的节点
                tplArr[tplId] = tpl.clone().removeClass('ui-droppable ui-draggable ui-draggable-handle item-shape-node').removeAttr('id').removeAttr('style');
            }else{
                layer.msg('您没有复制任何节点！');
            }
            return false;
        },
        clipboard: () => {
            layer.closeAll();
            // 将复制的模板数组转化成json对象数组
            tplJson = [];
            $.each(tplArr, function (i, v) {
                var json = { id: i, shape: v[0].outerHTML };
                tplJson.push(json);
            });
            // 打开剪贴板并操作
            layer.open({
                title: '剪贴板',
                shade: 0,
                offset: '100px',
                area: '50%',
                fixed: false,
                btn: [],
                content: '<div class="layui-table layui-hide" id="table" lay-filter="table"></div>',
                success: (index, layero) => {
                    // 渲染剪贴板中的数据
                    table.render({
                        elem: '#table',
                        data: tplJson, // 将tplArr中数组数据对应转化成table中json数据进行回显
                        page: true,
                        cols: [[
                            {type: 'radio', fixed: 'left'},
                            {field: 'id', title: '序号', align: 'center'},
                            {field: 'shape', title: '图形模板', align: 'center'},
                            {title: '操作', toolbar: '#bar', fixed: 'right', align: 'center'}
                        ]],
                        text: {
                            none: '暂无相关数据'
                        }
                    });
                }
            });
            return false;
        },
        help: () => {
            layer.open({
                title: '<i class="layui-icon layui-icon-help"></i>帮助',
                shade: 0,
                offset: '100px',
                area: ['400px', '600px'],
                fixed: false,
                btn: [],
                content: $('#helpText').html()
            });
        },
        drawLineMenu: (_id, _xy) => {
            // _id,无用的参数，_xy,是辨别哪个菜单的
            if (_xy && _xy.length > 0) {
                layer.tips($('#lineType').html(), '#lineColumn', {
                    tips: [2, '#00bcd4'],
                    time: 0
                });
                return false;
            }
            layer.tips($('#lineType').html(), '#drawLineMenu', {
                tips: [3, '#00bcd4'],
                time: 0
            });
        },
        nodeInfo: (_id) => {
            layer.closeAll();
            // 根据id获取到当前节点的json数据，方便表单进行数据回显
            var json = $('#' + _id).data('json');
            layer.open({
                title: false,
                // closeBtn: 0,
                type: 1,
                btn: [],
                offset: '100px',
                shade: 0,
                area: '50%',
                fixed: false,
                content: $('#nodeInfo').html(),
                success: (index, layero) => {
                    form.val('logic-form', json); // 表单数据回显操作
                    formSelects.render(); // 渲染formSelects插件
                    form.render(); // 渲染表单
                }
            });
        },
        findFirstStep: (_edges, _nodes) => {
            // 程序自找流程中第一步的步骤
            var rt = 1, s = [], t = [];
            if (_edges.length <= 0) {
                layer.msg('流程未封闭，请将所有的节点连好线，再提交');
                return false;
            }
            $.each(_edges, function (i, v) {
                s.push(v.parentStep);
                t.push(v.childStep);
            });
            $.each(_nodes, function (_i, _v) {
                if ($.inArray(_v.id, s.concat(t)) < 0) {
                    layer.msg('这个节点 <span style="color: red;">' + _v.name + '</span>未连接 流程未封闭，请检查！');
                    rt = 0;
                }
            });
            if (!rt) {
                return false;
            }
            Array.prototype.diff = function (a) {
                return this.filter(function (i) { return a.indexOf(i) < 0; });
            };
            var diffS = $.unique(s.sort()).diff($.unique(t.sort())), diffT = $.unique(t.sort()).diff($.unique(s.sort()));
            if (diffS.length > 1 || diffT.length > 1) {
                // 源节点和目标节点最多只能有一个
                layer.msg('流程未封闭！');
                rt = 0;
            }
            // 排序、去重最后合并差集，当diff的长度等于2时，默认第一个是源（即第一步），第二个是目标（即最后一步）
            var diff = diffS.concat(diffT);
            if (diff.length > 2) {
                // 当diff大于2时，说明流程未封闭
                var msg = '';
                $.each(diff, function (i, v) {
                    $.each(_nodes, function (_i, _v) {
                        if (_v.id === v) {
                            msg += _v.name + '、';
                        }
                    });
                });
                layer.msg('这几个节点 <span style="color: red;">' + msg + '</span> 有问题 流程未封闭，请检查！');
                rt = 0;
            } else if (diff.length === 2) {
                active.handleFirstStep(diff[0], _nodes);
            } else if (diff.length === 1 && diffS.length === 1) {
                active.handleFirstStep(diff[0], _nodes);
            } else if ((diff.length === 1 && diffS.length === 0) || diff.length === 0) {
                rt = 0;
                layer.prompt({
                    title: '流程图与第一步出现环路，程序无法判断第一步，请指定第一步',
                    skin: 'layer-ext-myskin',
                    closeBtn: 0,
                    btn: ['提交'],
                    content: '<form class="layui-form" style="padding: 20px;">\
									<label class="layui-form-label">指定第一步</label>\
									<div class="layui-input-block">\
										<select name="first" lay-search>\
										</select>\
									</div>\
                                </form>',
                    success: function () {
                        var opt = '<option value>请选择关联步骤</option>';
                        if ($.isArray(_nodes) && _nodes.length > 0) {
                            $.each(_nodes, function (_i, _v) {
                                opt += '<option data-cid="' + _v.id + '" value="' + _v.id + '">' + _v.name + '</option>';
                            });
                            $('select[name=first]').html(opt);
                        } else {
                            $('select[name=first]').html(opt);
                        }
                        form.render('select');
                    },
                    yes: function (_index) {
                        var v = $('select[name=first]').val();
                        if (v) {
                            active.handleFirstStep(v, _nodes);
                            if (!active.isReasonable()) {
                                return false;
                            }
                            localStorage.setItem('storage_items', JSON.stringify([_edges, _nodes]));
                            localStorage.setItem('storage_delIds', JSON.stringify([edgeIds, nodeIds]));
                            // history.back(-1);
                            // console.log(_edges, _nodes);
                            layer.close(_index);
                        } else {
                            layer.msg('请选择指定的第一步');
                        }
                    }
                });
            }
            return rt;
        },
        handleFirstStep: (_id, _nodes) => {
            // 处理得到的第一步数据排序，再将其他修改成0
            $.each(_nodes, function (i, v) {
                if (v.id === _id) {
                    v.listorder = 1;
                } else {
                    v.listorder = 0;
                }
            });
        },
        isReasonable: (_edges, _nodes) => {
            // 判断提交的流程图是否合理，主要判断是否出现多个判断的情况
            var rt = 1;
            $.each(_nodes, function (_i, _v) {
                var j = 0, b = 1;
                $.each(_edges, function (_in, _va) {
                    if (_v.id === _va.parentStep) {
                        if (_va.relationType == '2' && (_va.relationFlag == '1' || _va.relationFlag == '-1')) {
                            j++;
                            b = 0;
                        }
                    }
                });
                if (j !== 2 && b === 0) {
                    rt = 0;
                    layer.msg('文本为 <span style="color: red;">' + _v.name + '</span> 节点设计不规范，YES和NO应该成对出现！');
                    return false;
                }
            });
            return rt;
        }
    };

    jsPlumb.ready(function() {
        jsPlumb.setContainer('flow-container'); // 设置画布容器节点
        fun.cloneShapeNode();
        jsPlumb.bind('dblclick', function (_conn, _originalEvent) { // 双击链接线，触发删除链接线
			layer.confirm('确定要删除链接吗？', { icon: 3, title: '提示' }, function(_index) {
                jsPlumb.detach(_conn); // 删除连接线
                layer.close(_index);
			}, function() {
                layer.msg('你已取消删除此连线操作');
            });
		});

        // 将重新绘图的这部分代码放入 jsPlumb.ready 减少代码运行时报错的几率
        if (storage_items && $.isArray(storage_items)) {
            // 如果有，存储的状态应该是，[0]是edges,[1]nodes；都是数组
            if (storage_items[1] !== undefined && $.isArray(storage_items[1])) {
                $.each(storage_items[1], (i, v) => {
                    fun.renderShapeNode(v);
                });
            }
            // 顺序很重要，要先渲染节点，再连线
            if (storage_items[0] !== undefined && $.isArray(storage_items[0])) {
                $.each(storage_items[0], (i, v) => {
                    try { // 捕捉异常，处理可能出现的的报错
                        fun.drawLine(v);
                    } catch (error) {
                        console.info(error);
                        layer.msg('绘图不成功，请重新刷新！');
                        return false;
                    }
                });
            }
        }
        // 清除本地缓存数据
        localStorage.removeItem('storage_items');
        localStorage.removeItem('storage_delIds');
    });
    jsPlumb.importDefaults({
        ConnectionsDetachable: false // 是否拖动锚点断开连接
    });

    // 连线后触发的事件
    jsPlumb.bind("connection",function(_info){
        var conn = _info.connection;
        var label = conn.canvas.nextSibling ? conn.canvas.nextSibling.innerText : '';
        var relationType = 0, relationFlag = 0, relationAndOr = 0;
        switch (label) {
            case '或签':
                relationType = 1;
                relationAndOr = 1;
                break;
            case '':
                relationType = 1;
                relationAndOr = 2;
                break;
            case 'YES':
                relationType = 2;
                relationFlag = 1;
                break;
            case 'NO':
                relationType = 2;
                relationFlag = -1;
                break;
            case '回退/驳回':
                relationType = 3;
                break;
            default:
                break;
        }
        // 判断是回显的绘图数据还是重新手工添加的数据，如果是空对象，那么需要重新设置Parameters进去
        if ($.isEmptyObject(conn.getParameters())) {
            // 将连线信息整理好后，放置在Parameters参数中
            conn.setParameters({
                parentStep: conn.sourceId,
                childStep: conn.targetId,
                relationType,
                relationFlag,
                relationAndOr,
            });
        }
    });

    // 监听当连接断掉时的事件
    jsPlumb.bind('connectionDetached', (_info) => {
        var conn = _info.connection;
        var edgeInfo = conn.getParameters();
        if (edgeInfo.id !== undefined && edgeInfo.id) { // 如果线的id存在且不为空，则将其放入预先定义好的变量数组中
            edgeIds.push(edgeInfo.id); // 存储的数据主要用于后台删除无用的连接关系
        }
    });

    // 连接前的检查，判断是否建立连接
    jsPlumb.bind('beforeDrop', function (_info) {
        // 获取所有的连线信息
        var connInfo = jsPlumb.getAllConnections(), b = 1;
        if ($.isArray(connInfo) && connInfo.length > 0) {
            $.each(connInfo, function(i, v) {
                // 防止两个节点回环，如：1-2 变成 2-1
                var s = v.sourceId, t = v.targetId;
                if ((_info.sourceId === t && _info.targetId === s) || (_info.sourceId === s && _info.targetId === t)) {
                    b = 0;
                    return false;
                }
            });
        }
        if (b === 0) {
            return false;
        }
        if (_info.sourceId === _info.targetId) {
            // 避免连线源锚点和目标锚点在同一节点上(自连接)
            return false;
        }else{
            return true;
        }
    });

    // 监听提交步骤表单信息
    form.on('submit(saveConfig)', function (_data) {
        var field = _data.field, json = {};
        if (field.status === 'on') {
            field.status = 1;
        }
        if (field.status === undefined) {
            field.status = 0;
        }
        json = $.extend(true, $('#' + field.id).data('json'), field); // 深度合并两个对象数据
        $('#' + field.id).data('json', json); // 重新赋值会图形的data-json值
        $('#' + field.id + ' .nodeName').text(field.name); // 将新值赋值到nodeName的元素上
        $('#' + field.id + ' .nodeName').attr('title', field.name); // 将新值赋值到nodeName的元素上
        layer.closeAll();
        return false;
    });

    // 监听table中bar事件
    table.on('tool(table)', function (obj) {
        // console.log(obj);
        var id = obj.data.id;
        switch (obj.event) {
            case 'pasteTpl':
                pasteTplId = id;
                active.pasteNode();
                break;
            case 'del':
                tplArr = [];
                $.each(tplJson, function (i, v) {
                    if (v.id !== id) {
                        tplArr.push($(v.shape));
                    }
                });
                layer.msg('删除成功');
                break;
        };
        return false;
    });

    // 单击按钮触发事件
    $('.layui-btn').on('click', function (e) {
        var type = $(this).data('type');
        if (active[type]) {
            active[type].call(this, e);
        }
    });

    // 点击图形菜单时触发的事件
    $(document).on('click', '.shape-menu a', function () {
        var type = $(this).data('type'), id = $(this).data('id'), xy = $(this).data('xy');
        if (this.className.indexOf('layui-disabled') > -1) {
            // 如果点击的菜单有layui-disable这个类名的话，无法触发当前点击的方法
            return false;
        }
        if (active[type]) {
            active[type].call(this, id, xy);
        }
        return false;
    });

    // 操作按钮组的功能组
    $('.layui-btn-group .layui-btn').on('click', function() {
        var btnGroup = $('.layui-btn-group').children('.layui-btn');
        for(var i = 0; i < btnGroup.length; i++) {
            var ele = btnGroup[i];
            if ($(this).attr('title') === $(ele).attr('title')) {
                $(this).addClass('layui-btn-primary');
            }else{
                $(ele).removeClass('layui-btn-primary');
            }
        }
        return false;
    });

    $(document).on('click', '.lineType a', function() {
        var line = $(this).data('line');
        switch (line) {
            case 1:
                draw.config.connectorOverlays[1][1] = {};
                break;
            case 2:
                draw.config.connectorOverlays[1][1] = {
                    label: 'YES',
                    cssClass: 'jsplumb-label'
                };
                break;
            case 3:
                draw.config.connectorOverlays[1][1] = {
                    label: 'NO',
                    cssClass: 'jsplumb-label'
                };
                break;
            case 4:
                draw.config.connectorOverlays[1][1] = {
                    label: '回退/驳回',
                    cssClass: 'jsplumb-label'
                };
                break;
            case 5:
                draw.config.connectorOverlays[1][1] = {
                    label: '或签',
                    cssClass: 'jsplumb-label'
                };
                break;
            default:
                draw.config.connectorOverlays[1][1] = {};
                break;
        }
        layer.closeAll();
        return false;
    });

    // 单击节点相关事件操作
    $(document).on('click', '#flow-container .item-shape-node', function(e) {
        var nodeId = this.id;
        $('#' + this.id).addClass('shape-active'); // 点击节点时，增加高亮显示的样式
        var className = e.target.className; // 获取点击
        // console.log(className);
        if (className === 'layui-icon layui-icon-close-fill') {
            layer.confirm('是否要删除此节点', { icon: 3, title: '提示' }, function(_index) {
                active.delNode(nodeId);
                layer.close(_index);
            }, function() {
                layer.msg('你已取消删除此节点操作');
            });
        }
    });

    // 双击节点文本事件操作
    $(document).on('dblclick', '#flow-container .item-shape-node', function(e) {
        var t = ($(this).text()).replace(/(^\s*)|(\s*$)/g, ""); // 获取当前点击的节点文本值，并去除前后空格
        var className = e.target.className; // 获取点击
        var thisId = this.id; // 获取所点击的id
        if (className === 'nodeName') {
            layer.prompt({ title: '重新命名文本框', formType: 0, value: t }, function(_text, _index) {
                layer.close(_index);
                $('#' + thisId + ' .nodeName').text(_text); // 将新值赋值到所点击的元素上
                $('#' + thisId + ' .nodeName').attr('title', _text); // 将新值赋值到所点击的元素上
                var json = $.extend(true, $('#' + thisId).data('json'), { name: _text });
                $('#' + thisId).data('json', json);
            });
        }
    });

    // 监控键盘事件
    $(document).keydown(function (e) {
        // console.log(e.keyCode, e.key); // 键值和键名
        var allNode = $('#flow-container').children('.item-shape-node');
        if (e.keyCode === 27) { // 当用户按下esc时清空按钮组的样式
            $('.layui-btn-group .layui-btn').removeClass('layui-btn-primary');
        }
        if (e.ctrlKey && e.keyCode === 86) { // 组合键的判断 Ctrl+v
            // 会粘贴一个坐标在[100,100]，以容器左上角为原点节点
            active.pasteNode();
        }
        if (e.ctrlKey && e.keyCode === 67) { // 组合键的判断 Ctrl+c
            // ctrl+c键 按下时，会copy高亮显示的一个节点
            $.each(allNode, function (i, v) {
                var nodeClassName = v.className.split(' ');
                $.each(nodeClassName, function (_i, _v) {
                    if (_v.indexOf('shape-active') > -1) {
                        active.copyNode(v.id);
                    }
                });
            });
        }
        if (e.ctrlKey && e.keyCode === 90) { // 组合键的判断 Ctrl+v
            console.log('你按下了ctrl+Z，撤销');
        }
        if (e.keyCode === 46) {
            // delte键 按下时，会删除高亮显示的一个节点
            $.each(allNode, function(i, v) {
                var nodeClassName = v.className.split(' ');
                $.each(nodeClassName, function(_i, _v) {
                     if (_v.indexOf('shape-active') > -1) {
                        active.del(v.id);
                    }
                });
            });
        }
    });

    // 鼠标右键功能
    // 禁用某元素组织右键点击事件
    $(document).on("contextmenu", "div", function () {
        return false;
    });

    // 激活item-shape-node右键功能，弹出节点菜单
    $(document).on('mousedown', '#flow-container .item-shape-node', function (e) {
        var k = e.which, id = this.id;
        if (3 === k) {
            var x = e.clientX;
            var y = e.clientY;
            layer.closeAll();
            var html = '<div class="shape-menu">\
                            <a href="javascript:;" data-id="' + id + '" data-type="nodeInfo">编辑节点信息</a>\
                            <a href="javascript:;" data-id="' + id + '" data-type="editNodeName">编辑节点名称</a>\
                            <a href="javascript:;" data-id="' + id + '" data-type="editForm">编辑表单</a>\
                            <a href="javascript:;" data-id="' + id + '" data-type="copyNode">复制节点</a>\
                            <a href="javascript:;" data-id="' + id + '" data-type="del">删除节点</a>\
                        </div>';
            layer.open({
                title: false,
                closeBtn: 0,
                type: 1,
                shade: 0,
                offset: [y, x],
                area: ['120px'],
                fixed: false,
                content: html
            });
        }
        // 一定要，防止事件冒泡
        return false;
    });

    $(document).on('mousedown', '#flow-container', function(e) {
        var k = e.which, isAddClass = '';
        if (!$.isArray(tplArr) || tplArr.length <= 0) {
            // 当存储复制模板的变量数组长度是0时，则需要添加禁止操作的类名
            isAddClass = 'layui-disabled';
        }
        if (3 === k) {
            layer.closeAll();
            var x = e.clientX, y = e.clientY;
            var xy = JSON.stringify([e.offsetX, e.offsetY]); // 获取相对于容器的xy坐标
            var html = '<div class="shape-menu container-menu">\
                            <a href="javascript:;" class="' + isAddClass + '" data-xy="' + xy + '" data-type="pasteNode">粘贴节点</a>\
                            <a href="javascript:;" class="' + isAddClass + '" data-xy="' + xy + '" data-type="clipboard">打开剪贴板</a>\
                            <a href="javascript:;" data-xy="' + xy + '" data-type="drawLineMenu" id="lineColumn">连线类别<i class="layui-icon layui-icon-triangle-r"></i></a>\
                        </div>';
            layer.open({
                title: false,
                closeBtn: 0,
                type: 1,
                shade: 0,
                offset: [y, x],
                area: ['120px'],
                fixed: false,
                content: html
            });
        }
        return false;
    });

    // 单击鼠标任何键位，撤销弹出层
    $('div').on('mousedown', function (e) {
        layer.closeAll();
        $('.item-shape-node').removeClass('shape-active'); // 点击不属于图形节点时撤销高亮样式
    });

});
</script>
</body>
</html>